import type { CrudBinding, RuleRecord } from '@fast-crud/fast-crud';

import type { AxiosRequestConfig } from '@vben/request';

import type { VxeTablePropTypes } from '#/adapter/vxe-table';
import type {
  ConstraintInfo,
  FieldValidatorDesc,
  FormRulesExt,
  RuleMatchByFs,
} from '#/api';
import type { ActionEnum } from '#/enums/commonEnum';

import consola from 'consola';
import { cloneDeep, isArray, isBoolean, split } from 'lodash-es';

import {
  BACKEND_NUMBER,
  compareDate2Now,
  getMessage,
  RuleType,
  ruleTypeMap,
} from '#/api';
import { ServicePrefixEnum } from '#/enums/commonEnum';
import { useMessage } from '#/hooks/web/useMessage';

import { requestClient } from '../request';
// 全局的规则缓存，页面刷新时重置
const RULE_MAP_CACHE = new Map();
const VXE_RULE_MAP_CACHE = new Map();

const { createMessage } = useMessage();

/** 规则列表 */
const list: RuleMatchByFs[] = [
  {
    match: (type) => {
      return type === 'RegEx';
    },
    callback: ({ fieldRules, attrs, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_, value) {
          if (!value) {
            return Promise.resolve();
          }
          const regexp = new RegExp(attrs.regexp);
          if (!regexp.test(value)) {
            return Promise.reject(getMessage(attrs));
          }
          return Promise.resolve();
        },
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'Max';
    },
    callback: ({ fieldRules, attrs, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value) {
          if (Number.parseInt(value, 10) > Number.parseInt(attrs.value, 10)) {
            return Promise.reject(getMessage(attrs));
          }
          return Promise.resolve();
        },
        message: getMessage(attrs),
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'Min';
    },
    callback: ({ fieldRules, attrs, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value, _) {
          if (Number.parseInt(value, 10) < Number.parseInt(attrs.value, 10)) {
            return Promise.reject(getMessage(attrs));
          }
          return Promise.resolve();
        },
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'DecimalMax';
    },
    callback: ({ fieldRules, attrs, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value, _) {
          if (Number.parseFloat(value) > Number.parseFloat(attrs.value)) {
            return Promise.reject(getMessage(attrs));
          }
          return Promise.resolve();
        },
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'DecimalMin';
    },
    callback: ({ fieldRules, attrs, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value, _) {
          if (Number.parseFloat(value) < Number.parseFloat(attrs.value)) {
            return Promise.reject(getMessage(attrs));
          }
          return Promise.resolve();
        },
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'Null';
    },
    callback: ({ fieldRules, attrs, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value, _) {
          if (value && value.length > 0) {
            return Promise.reject(getMessage(attrs));
          }
          return Promise.resolve();
        },
        message: getMessage(attrs),
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'NotNull';
    },
    callback: ({ fieldRules, attrs, fieldType, trigger }) => {
      const typ = ruleTypeMap.get(fieldType) || 'string';
      fieldRules.push({
        required: true,
        type: typ,
        trigger,
        whitespace: true,
        transform(value) {
          if (isBoolean(value)) {
            return value;
          }
          return value !== undefined &&
            value !== null &&
            value.toString().trim() !== ''
            ? String(value)
            : value;
        },
        message: getMessage(attrs),
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'Range';
    },
    callback: ({ fieldRules, attrs, trigger }) => {
      fieldRules.push({
        max: attrs.max,
        min: attrs.min,
        trigger,
        message: getMessage(attrs),
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'AssertTrue';
    },
    callback: ({ fieldRules, attrs, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value, _) {
          if (value === 'true' || value === true) {
            return Promise.resolve();
          }
          return Promise.reject(getMessage(attrs));
        },
        message: getMessage(attrs),
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'AssertFalse';
    },
    callback: ({ fieldRules, attrs, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value, _) {
          if (value === 'false' || value === false) {
            return Promise.resolve();
          }
          return Promise.reject(getMessage(attrs));
        },
        message: getMessage(attrs),
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'Negative';
    },
    callback: ({ fieldRules, attrs, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value, _) {
          if (value) {
            if (Number(value) < 0) {
              return Promise.resolve();
            }
            return Promise.reject(getMessage(attrs));
          }
          return Promise.resolve();
        },
        message: getMessage(attrs),
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'NegativeOrZero';
    },
    callback: ({ fieldRules, attrs, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value, _) {
          if (value) {
            if (Number(value) <= 0) {
              return Promise.resolve();
            }
            return Promise.reject(getMessage(attrs));
          }
          return Promise.resolve();
        },
        message: getMessage(attrs),
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'Positive';
    },
    callback: ({ fieldRules, attrs, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value, _) {
          if (value) {
            if (Number(value) > 0) {
              return Promise.resolve();
            }
            return Promise.reject(getMessage(attrs));
          }
          return Promise.resolve();
        },
        message: getMessage(attrs),
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'PositiveOrZero';
    },
    callback: ({ fieldRules, attrs, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value, _) {
          if (value) {
            if (Number(value) >= 0) {
              return Promise.resolve();
            }
            return Promise.reject(getMessage(attrs));
          }
          return Promise.resolve();
        },
        message: getMessage(attrs),
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'Past';
    },
    callback: ({ fieldRules, attrs, fieldType, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value, _) {
          if (compareDate2Now(value, fieldType, 'Past')) {
            return Promise.resolve();
          }
          return Promise.reject(getMessage(attrs));
        },
        message: getMessage(attrs),
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'PastOrPresent';
    },
    callback: ({ fieldRules, attrs, fieldType, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value, _) {
          if (compareDate2Now(value, fieldType, 'PastOrPresent')) {
            return Promise.resolve();
          }
          return Promise.reject(getMessage(attrs));
        },
        message: getMessage(attrs),
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'Digits';
    },
    callback: ({ fieldRules, attrs, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value, _) {
          const numVal = Number(value);
          if (/.*\..*/.test(numVal.toString())) {
            const [zsj, xsj] = split(numVal.toString(), '.');
            const integer = zsj && zsj.length > attrs.integer;
            const fraction = xsj && xsj.length > attrs.fraction;

            if (integer || fraction) {
              return Promise.reject(getMessage(attrs));
            }
            return Promise.resolve();
          }
          if (numVal.toString().length > attrs.integer) {
            return Promise.reject(getMessage(attrs));
          }
          return Promise.resolve();
        },
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'Future';
    },
    callback: ({ fieldRules, attrs, fieldType, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value, _) {
          if (compareDate2Now(value, fieldType, 'Future')) {
            return Promise.resolve();
          }
          return Promise.reject(getMessage(attrs));
        },
        message: getMessage(attrs),
      });
    },
  },
  {
    match: (type: string) => {
      return type === 'FutureOrPresent';
    },
    callback: ({ fieldRules, attrs, fieldType, trigger }) => {
      fieldRules.push({
        type: 'method',
        trigger,
        asyncValidator(_rule, value, _) {
          if (compareDate2Now(value, fieldType, 'FutureOrPresent')) {
            return Promise.resolve();
          }
          return Promise.reject(getMessage(attrs));
        },
        message: getMessage(attrs),
      });
    },
  },
];

/**
 * 解析字段的校验规则
 *
 * @param fieldRules 字段约束
 * @param constraints 约束
 * @param fieldType 字段类型
 * @param trigger 触发条件
 */
function decodeRules({
  fieldRules,
  constraints,
  fieldType,
  trigger,
}: {
  constraints: ConstraintInfo[];
  fieldRules: RuleRecord[];
  fieldType: string;
  trigger?: 'blur' | 'change' | Array<'blur' | 'change'>;
}) {
  constraints &&
    constraints.forEach(({ type, attrs }) => {
      list.some(({ match, callback }) => {
        if (match(type)) {
          callback({ fieldRules, attrs, fieldType, trigger });
          return true;
        }
        return false;
      });
    });
}

/**
 * 解析所有字段名
 *
 * @param data 后端返回的值
 */
function transformationRules(
  data: FieldValidatorDesc[],
  trigger: 'blur' | 'change' | Array<'blur' | 'change'> = ['blur', 'change'],
): Record<string, RuleRecord[]> {
  const validateRules: Record<string, RuleRecord[]> = {};
  data.forEach(({ name, field, fieldType, constraints }) => {
    const fieldRules: RuleRecord[] = [];
    if (BACKEND_NUMBER.includes(fieldType)) {
      fieldRules.push({
        type: 'number',
        trigger,
        transform(value: any) {
          if (
            value !== null &&
            value !== undefined &&
            value.toString().trim().length > 0
          ) {
            return Number(value);
          }
          return value;
        },
        message: `${name}必须是数字`,
      });
    } else if (fieldType === 'Boolean') {
      fieldRules.push({
        type: 'boolean',
        trigger,
        transform(value: any) {
          if (
            value !== null &&
            value !== undefined &&
            value.toString().trim().length > 0
          ) {
            return Boolean(value);
          }
          return value;
        },
        message: `${name}必须是布尔类型`,
      });
    }
    decodeRules({ fieldRules, constraints, fieldType, trigger });

    validateRules[field] = fieldRules;
  });
  return validateRules;
}

/** 前端的规则和后端规则进行合并 */
function mergeRules(
  formSchemaRules: Record<string, RuleRecord | RuleRecord[]>,
  customFormSchemaRules: FormRulesExt = {},
): Record<string, RuleRecord | RuleRecord[]> {
  if (!formSchemaRules && !customFormSchemaRules) {
    return {};
  }

  const ruleMap: Record<string, RuleRecord | RuleRecord[]> =
    cloneDeep(formSchemaRules);
  Object.entries(customFormSchemaRules).forEach(([field, fieldRules]) => {
    // 后端没有该字段的校验规则，则前端和后端合并
    if (formSchemaRules[field]) {
      const { type, rules } = fieldRules;
      // 覆盖
      if (type === RuleType.cover) {
        ruleMap[field] = isArray(rules) ? rules : [rules];
      } else if (type === RuleType.and) {
        // 追加
        const frontRules = isArray(rules) ? rules : [rules];
        const backendRules = (formSchemaRules[field] || []) as RuleRecord[];
        ruleMap[field] = [...backendRules, ...frontRules];
      } else {
        // 忽略
        ruleMap[field] = formSchemaRules[field];
      }
    } else {
      // 后端没有该字段的校验规则，则完全使用前端的规则
      ruleMap[field] = fieldRules.rules;
    }
  });

  return ruleMap;
}

function buildCrudOption(
  mode: ActionEnum,
  rules: Record<string, RuleRecord | RuleRecord[]>,
): CrudBinding {
  return { [`${mode}Form`]: { rules } };
}

/**
 * 从后端获取某个接口基于 Hibernate Validator 注解生成 fast-crud 的参数校验规则
 *
 * @param param 参数
 * @param param.Api url和method
 * @param param.customRules 自定义规则
 * @param param.mode add还是edit
 * @param param.trigger 触发时机
 */
export const getValidateRulesByFs = async (param: {
  Api?: AxiosRequestConfig;
  customRules?: FormRulesExt;
  mode?: ActionEnum;
  trigger?: 'blur' | 'change' | Array<'blur' | 'change'>;
}): Promise<CrudBinding> => {
  const { Api, mode, customRules, trigger } = param;
  let res = null;
  let key;
  if (Api) {
    const formValidateApi = { url: '', method: Api.method?.toLowerCase() };

    for (const [_, value] of Object.entries(ServicePrefixEnum)) {
      if (Api.url?.startsWith(value)) {
        formValidateApi.url = Api.url.replace(value, `${value}/form/validator`);
      }
    }

    key = formValidateApi.url + formValidateApi.method;
    if (RULE_MAP_CACHE.has(key)) {
      return RULE_MAP_CACHE.get(key);
    }

    res = await requestClient.request2<FieldValidatorDesc[]>(formValidateApi);
  }
  try {
    let formSchemaRules = {};
    if (res) {
      formSchemaRules = transformationRules(res, trigger);
    }
    const rules = mergeRules(formSchemaRules, customRules);

    if (mode) {
      const formOptions = buildCrudOption(mode, rules);
      RULE_MAP_CACHE.set(key, formOptions);
      return formOptions;
    }
    RULE_MAP_CACHE.set(key, rules);
    return rules;

    return {};
  } catch (error) {
    consola.error(error);
    createMessage.error('获取表单校验异常');
    return {};
  }
};

/**
 * 从后端获取某个接口基于 Hibernate Validator 注解生成的 vxe-table 的参数校验规则
 *
 * @param Api url和method
 * @param customRules 自定义规则
 */
export const getValidateRuleByVxe = async (
  Api: AxiosRequestConfig,
  customRules?: FormRulesExt,
): Promise<VxeTablePropTypes.EditRules> => {
  const formValidateApi = { url: '', method: Api.method?.toLowerCase() };

  for (const [_, value] of Object.entries(ServicePrefixEnum)) {
    if (Api.url?.startsWith(value)) {
      formValidateApi.url = Api.url.replace(value, `${value}/form/validator`);
    }
  }

  try {
    if (!formValidateApi.url) {
      createMessage.error('ServicePrefixEnum枚举类未正确配置！');
      return {};
    }

    const key = formValidateApi.url + formValidateApi.method;
    if (VXE_RULE_MAP_CACHE.has(key)) {
      return VXE_RULE_MAP_CACHE.get(key);
    }

    const res = await requestClient.request2<FieldValidatorDesc[]>({
      ...formValidateApi,
    });

    if (res) {
      const formSchemaRules = transformationRules(res);
      const rules = mergeRules(formSchemaRules, customRules);

      VXE_RULE_MAP_CACHE.set(key, rules);
      return rules as VxeTablePropTypes.EditRules;
    }
  } catch {}
  return {};
};
